#include "stdafx.h"
#include "CGraphic.h"
#include "Dx11Utils.h"
#include "resource.h"

using namespace DirectX;

namespace tri
{
	struct QuadVertex
	{
		float x, y, z;
		float u, v;
	};

	CGraphic::CGraphic()
	{
		m_hInst = nullptr;
		m_hWnd = nullptr;
		m_driver_type = D3D_DRIVER_TYPE_NULL;
		m_feature_level = D3D_FEATURE_LEVEL_11_0;
		m_d3d_device = nullptr;
		m_d3d_device1 = nullptr;
		m_immediate_context = nullptr;
		m_immediate_context1 = nullptr;
		m_swap_chain = nullptr;
		m_swap_chain1 = nullptr;
		m_main_render_target_view = nullptr;
		m_frame_buffer_texture = nullptr;
		m_sampler = nullptr;
		m_frame_buffer = nullptr;
		m_quad_vertex_buffer = nullptr;
		m_quad_layout = nullptr;
		m_quad_vs = nullptr;
		m_quad_ps = nullptr;
	}

	CGraphic::~CGraphic()
	{

	}

	HRESULT CGraphic::InitWindow(HINSTANCE hInstance, int nCmdShow)
	{
		// Register class
		WNDCLASSEX wcex;
		wcex.cbSize = sizeof(WNDCLASSEX);
		wcex.style = CS_HREDRAW | CS_VREDRAW;
		wcex.lpfnWndProc = WndProc;
		wcex.cbClsExtra = 0;
		wcex.cbWndExtra = 0;
		wcex.hInstance = hInstance;
		wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_TUTORIAL1);
		wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
		wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
		wcex.lpszMenuName = nullptr;
		wcex.lpszClassName = L"TutorialWindowClass";
		wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_TUTORIAL1);
		if (!RegisterClassEx(&wcex))
			return E_FAIL;

		// Create window
		m_hInst = hInstance;
		RECT rc = { 0, 0, 800, 600 };
		AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
		m_hWnd = CreateWindow(L"TutorialWindowClass", L"Direct3D 11 Tutorial 1: Direct3D 11 Basics",
			WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
			CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, nullptr, nullptr, hInstance,
			nullptr);
		if (!m_hWnd)
			return E_FAIL;

		ShowWindow(m_hWnd, nCmdShow);

		return S_OK;
	}

	HRESULT CGraphic::InitDevice()
	{
		HRESULT hr = S_OK;

		RECT rc;
		GetClientRect(m_hWnd, &rc);
		UINT width = rc.right - rc.left;
		UINT height = rc.bottom - rc.top;

		UINT createDeviceFlags = 0;
#ifdef _DEBUG
		createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

		D3D_DRIVER_TYPE driverTypes[] =
		{
			D3D_DRIVER_TYPE_HARDWARE,
			D3D_DRIVER_TYPE_WARP,
			D3D_DRIVER_TYPE_REFERENCE,
		};
		UINT numDriverTypes = ARRAYSIZE(driverTypes);

		D3D_FEATURE_LEVEL featureLevels[] =
		{
			D3D_FEATURE_LEVEL_11_1,
			D3D_FEATURE_LEVEL_11_0,
			D3D_FEATURE_LEVEL_10_1,
			D3D_FEATURE_LEVEL_10_0,
		};
		UINT numFeatureLevels = ARRAYSIZE(featureLevels);

		for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++)
		{
			m_driver_type = driverTypes[driverTypeIndex];
			hr = D3D11CreateDevice(nullptr, m_driver_type, nullptr, createDeviceFlags, featureLevels, numFeatureLevels,
				D3D11_SDK_VERSION, &m_d3d_device, &m_feature_level, &m_immediate_context);

			if (hr == E_INVALIDARG)
			{
				// DirectX 11.0 platforms will not recognize D3D_FEATURE_LEVEL_11_1 so we need to retry without it
				hr = D3D11CreateDevice(nullptr, m_driver_type, nullptr, createDeviceFlags, &featureLevels[1], numFeatureLevels - 1,
					D3D11_SDK_VERSION, &m_d3d_device, &m_feature_level, &m_immediate_context);
			}

			if (SUCCEEDED(hr))
				break;
		}
		if (FAILED(hr))
			return hr;

		// Obtain DXGI factory from device (since we used nullptr for pAdapter above)
		IDXGIFactory1* dxgiFactory = nullptr;
		{
			IDXGIDevice* dxgiDevice = nullptr;
			hr = m_d3d_device->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&dxgiDevice));
			if (SUCCEEDED(hr))
			{
				IDXGIAdapter* adapter = nullptr;
				hr = dxgiDevice->GetAdapter(&adapter);
				if (SUCCEEDED(hr))
				{
					hr = adapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(&dxgiFactory));
					adapter->Release();
				}
				dxgiDevice->Release();
			}
		}
		if (FAILED(hr))
			return hr;

		// Create swap chain
		IDXGIFactory2* dxgiFactory2 = nullptr;
		hr = dxgiFactory->QueryInterface(__uuidof(IDXGIFactory2), reinterpret_cast<void**>(&dxgiFactory2));
		if (dxgiFactory2)
		{
			// DirectX 11.1 or later
			hr = m_d3d_device->QueryInterface(__uuidof(ID3D11Device1), reinterpret_cast<void**>(&m_d3d_device1));
			if (SUCCEEDED(hr))
			{
				(void)m_immediate_context->QueryInterface(__uuidof(ID3D11DeviceContext1), reinterpret_cast<void**>(&m_immediate_context1));
			}

			DXGI_SWAP_CHAIN_DESC1 sd;
			ZeroMemory(&sd, sizeof(sd));
			sd.Width = width;
			sd.Height = height;
			sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
			sd.SampleDesc.Count = 1;
			sd.SampleDesc.Quality = 0;
			sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
			sd.BufferCount = 1;

			

			hr = dxgiFactory2->CreateSwapChainForHwnd(m_d3d_device, m_hWnd, &sd, nullptr, nullptr, &m_swap_chain1);
			if (SUCCEEDED(hr))
			{
				hr = m_swap_chain1->QueryInterface(__uuidof(IDXGISwapChain), reinterpret_cast<void**>(&m_swap_chain));
			}

			dxgiFactory2->Release();
		}
		else
		{
			// DirectX 11.0 systems
			DXGI_SWAP_CHAIN_DESC sd;
			ZeroMemory(&sd, sizeof(sd));
			sd.BufferCount = 1;
			sd.BufferDesc.Width = width;
			sd.BufferDesc.Height = height;
			sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
			sd.BufferDesc.RefreshRate.Numerator = 60;
			sd.BufferDesc.RefreshRate.Denominator = 1;
			sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
			sd.OutputWindow = m_hWnd;
			sd.SampleDesc.Count = 1;
			sd.SampleDesc.Quality = 0;
			sd.Windowed = TRUE;

			hr = dxgiFactory->CreateSwapChain(m_d3d_device, &sd, &m_swap_chain);
		}

		// Note this tutorial doesn't handle full-screen swapchains so we block the ALT+ENTER shortcut
		dxgiFactory->MakeWindowAssociation(m_hWnd, DXGI_MWA_NO_ALT_ENTER);

		dxgiFactory->Release();

		if (FAILED(hr))
			return hr;

		// Create a render target view
		ID3D11Texture2D* pBackBuffer = nullptr;
		hr = m_swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&pBackBuffer));
		if (FAILED(hr))
			return hr;

		hr = m_d3d_device->CreateRenderTargetView(pBackBuffer, nullptr, &m_main_render_target_view);
		pBackBuffer->Release();
		if (FAILED(hr))
			return hr;

		m_immediate_context->OMSetRenderTargets(1, &m_main_render_target_view, nullptr);

		// Setup the viewport
		D3D11_VIEWPORT vp;
		vp.Width = (FLOAT)width;
		vp.Height = (FLOAT)height;
		vp.MinDepth = 0.0f;
		vp.MaxDepth = 1.0f;
		vp.TopLeftX = 0;
		vp.TopLeftY = 0;
		m_immediate_context->RSSetViewports(1, &vp);

		D3D11_TEXTURE2D_DESC texture_desc;
		memset(&texture_desc, 0, sizeof(texture_desc));
		texture_desc.Width = width;
		texture_desc.Height = height;
		texture_desc.MipLevels = 1;
		texture_desc.ArraySize = 1;
		texture_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		texture_desc.SampleDesc = DXGI_SAMPLE_DESC{ 1, 0 };
		texture_desc.Usage = D3D11_USAGE_DEFAULT;
		texture_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
		texture_desc.CPUAccessFlags = 0;
		texture_desc.MiscFlags = 0;
		m_d3d_device->CreateTexture2D(&texture_desc, nullptr, &m_frame_buffer_texture);

		D3D11_SAMPLER_DESC sampler_desc;
		memset(&sampler_desc, 0, sizeof(sampler_desc));
		sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
		sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
		sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
		sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER;
		sampler_desc.MinLOD = 0;
		sampler_desc.MaxLOD = D3D11_FLOAT32_MAX;
		m_d3d_device->CreateSamplerState(&sampler_desc, &m_sampler);

		D3D11_SHADER_RESOURCE_VIEW_DESC shader_view_desc;
		memset(&shader_view_desc, 0, sizeof(shader_view_desc));
		shader_view_desc.Format = texture_desc.Format;
		shader_view_desc.ViewDimension = D3D_SRV_DIMENSION_TEXTURE2D;
		shader_view_desc.Texture2D.MostDetailedMip = 0;
		shader_view_desc.Texture2D.MipLevels = 1;
		m_d3d_device->CreateShaderResourceView(m_frame_buffer_texture, &shader_view_desc, &m_frame_buffer_shader_view);

		const uint32 FRAME_BUFFER_SIZE = width * height;
		m_frame_buffer = new int[FRAME_BUFFER_SIZE];
		memset(m_frame_buffer, 0x88, sizeof(int)*FRAME_BUFFER_SIZE);

		QuadVertex quad_vertexs[6] = {
			{ -1.0f, -1.0f, 0.0f, 0.0f, 1.0f },
			{ -1.0f, 1.0f, 0.0f, 0.0f, 0.0f },
			{ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f },
			{ 1.0f, -1.0f, 0.0f, 1.0f, 1.0f },
			{ -1.0f, -1.0f, 0.0f, 0.0f, 1.0f },
			{ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f },
		};

		D3D11_BUFFER_DESC quad_vb_desc;
		memset(&quad_vb_desc, 0, sizeof(quad_vb_desc));
		quad_vb_desc.Usage = D3D11_USAGE_DEFAULT;
		quad_vb_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
		quad_vb_desc.ByteWidth = sizeof(quad_vertexs);
		quad_vb_desc.CPUAccessFlags = 0;
		quad_vb_desc.MiscFlags = 0;
		m_d3d_device->CreateBuffer(&quad_vb_desc, nullptr, &m_quad_vertex_buffer);
		m_immediate_context->UpdateSubresource(m_quad_vertex_buffer, 0, nullptr, quad_vertexs, sizeof(quad_vertexs), 0);

		ID3DBlob* vs_blob = nullptr;
		hr = CompileShaderFromFile(L"../Shader/quad_vs.hlsl", "QuadVS", "vs_5_0", &vs_blob);
		if (FAILED(hr))
		{
			MessageBox(nullptr, L"The quad_vs.hlsl cannot be compiled.", L"Error", MB_OK);
			return hr;
		}
		hr = m_d3d_device->CreateVertexShader(vs_blob->GetBufferPointer(), vs_blob->GetBufferSize(), nullptr, &m_quad_vs);
		if (FAILED(hr))
			return hr;

		D3D11_INPUT_ELEMENT_DESC quad_layout[] =
		{
			{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
			{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		};
		m_d3d_device->CreateInputLayout(quad_layout, sizeof(quad_layout) / sizeof(quad_layout[0]), vs_blob->GetBufferPointer(), vs_blob->GetBufferSize(), &m_quad_layout);
		vs_blob->Release();

		ID3DBlob* ps_blob = nullptr;
		hr = CompileShaderFromFile(L"../Shader/quad_ps.hlsl", "QuadPS", "ps_5_0", &ps_blob);
		if (FAILED(hr))
		{
			MessageBox(nullptr, L"The quad_ps.hlsl cannot be compiled.", L"Error", MB_OK);
			return hr;
		}
		hr = m_d3d_device->CreatePixelShader(ps_blob->GetBufferPointer(), ps_blob->GetBufferSize(), nullptr, &m_quad_ps);
		ps_blob->Release();

		m_immediate_context->VSSetShader(m_quad_vs, nullptr, 0);
		m_immediate_context->PSSetShader(m_quad_ps, nullptr, 0);
		m_immediate_context->IASetInputLayout(m_quad_layout);
		m_immediate_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
		uint32 stride = sizeof(QuadVertex);
		uint32 offset = 0;
		m_immediate_context->IASetVertexBuffers(0, 1, &m_quad_vertex_buffer, &stride, &offset);

		m_immediate_context->PSSetShaderResources(0, 1, &m_frame_buffer_shader_view);
		m_immediate_context->PSSetSamplers(0, 1, &m_sampler);
		return S_OK;
	}

	void CGraphic::CleanupDevice()
	{
		if (m_immediate_context) m_immediate_context->ClearState();

		if (m_main_render_target_view) m_main_render_target_view->Release();
		if (m_swap_chain1) m_swap_chain1->Release();
		if (m_swap_chain) m_swap_chain->Release();
		if (m_immediate_context1) m_immediate_context1->Release();
		if (m_immediate_context) m_immediate_context->Release();
		if (m_d3d_device1) m_d3d_device1->Release();
		if (m_d3d_device) m_d3d_device->Release();
		if (m_frame_buffer_texture) m_frame_buffer_texture->Release();
		if (m_sampler) m_sampler->Release();
		if (m_frame_buffer_shader_view) m_frame_buffer_shader_view->Release();
		if (m_quad_vertex_buffer) m_quad_vertex_buffer->Release();
		if (m_quad_layout) m_quad_layout->Release();
		if (m_quad_vs) m_quad_vs->Release();
		if (m_quad_ps) m_quad_ps->Release();

		if (m_frame_buffer)
		{
			delete m_frame_buffer;
			m_frame_buffer = false;
		}
	}

	LRESULT CALLBACK CGraphic::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		PAINTSTRUCT ps;
		HDC hdc;

		switch (message)
		{
		case WM_PAINT:
			hdc = BeginPaint(hWnd, &ps);
			EndPaint(hWnd, &ps);
			break;

		case WM_DESTROY:
			PostQuitMessage(0);
			break;

			// Note that this tutorial does not handle resizing (WM_SIZE) requests,
			// so we created the window without the resize border.

		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}

		return 0;
	}

	void CGraphic::Render()
	{
		D3D11_TEXTURE2D_DESC frame_tex_desc;
		m_frame_buffer_texture->GetDesc(&frame_tex_desc);
		uint32 pitch = frame_tex_desc.Width * sizeof(int);

		m_immediate_context->UpdateSubresource(m_frame_buffer_texture, 0, nullptr, m_frame_buffer, pitch, 0);

		m_immediate_context->ClearRenderTargetView(m_main_render_target_view, Colors::MidnightBlue);
		m_immediate_context->Draw(6, 0);
		m_swap_chain->Present(0, 0);
	}

}
